package camid.services.cloudmanager;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import camid.distribution.lifecycle.LifecycleScope;
import camid.distribution.lifecycle.LocationScope;
import camid.distribution.lifecycle.ManagedObject;
import camid.util.monitoring.NodeEvaluator;
import camid.util.monitoring.NodeMetrics;

@ManagedObject( lifecycleScope = LifecycleScope.STATIC,  locationScope = LocationScope.NODE )
public class ClusterManagerRegistry implements IClusterManagerRegistry {

	private Map<String, NodeEvaluator> nodeEvaluatorMap;
	
	private Map<String, Double> nodeCostMap;
	
	private NodeAverageCost averageCost;
	
	public static final Double NUMBER_OBJECTS_WEIGHT = 0.1;
	
	public static final Double RESPONSE_TIME_WEIGHT = 1.0;
	
	public static final Double THROUGHPUT_WEIGHT = 0.35;
	
	public static final Double CPU_WEIGHT = 0.10;
	
	public static final Double MEM_WEIGHT = 0.10;

	public static final String CLUSTER_MANAGER_REGISTRY_ID = "ClusterManagerRegistry";
	
	public ClusterManagerRegistry()
	{
		nodeEvaluatorMap = new TreeMap<String, NodeEvaluator> ();
		nodeCostMap = new TreeMap<String, Double> ();
		averageCost = new NodeAverageCost(0.0, 0.0);
	}
	
	@Override
	public Double sendNodeMetrics(String nodeId, NodeMetrics nodeMetrics) 
	{	
		NodeEvaluator evaluator = null;
		if(nodeEvaluatorMap.containsKey(nodeId))
		{
			evaluator = nodeEvaluatorMap.get(nodeId);
		}
		else
		{
			evaluator = createNodeEvaluator();
			nodeEvaluatorMap.put(nodeId, evaluator);
		}
		Double cost = evaluator.feedMetric(nodeMetrics);
		nodeCostMap.put(nodeId, cost);
		
		evaluateAverage();
		
		return cost;
	}
	
	private void evaluateAverage()
	{
		Double sum = 0.0;
		
		Double nodeCount = (double)nodeCostMap.size();
		
		for( Entry<String,Double> costEntry : nodeCostMap.entrySet() )
		{
			sum += costEntry.getValue();
		}
		
		Double average = sum / nodeCount;
		
		Double diffDoubleSum = 0.0;
		
		for( Entry<String,Double> costEntry : nodeCostMap.entrySet() )
		{
			Double cost = costEntry.getValue();
			diffDoubleSum += (cost - average) * (cost - average); 
		}
		
		Double deviation = Math.sqrt( diffDoubleSum / nodeCount );
		
		this.averageCost = new NodeAverageCost(average, deviation);
		
	}
	
	@Override
	public Double getNodeCost(String nodeId)
	{
		Double result = 0.0;
		if(nodeCostMap.containsKey(nodeId))
		{
			result = nodeCostMap.get(nodeId);
		}
		return result;
	}
	
	@Override
	public Map<String, Double> getCostMap(List<String> nodeIds)
	{
		Map<String,Double> costMap = new TreeMap<String, Double> ();
		for(String nodeId : nodeIds)
		{
			Double cost = getNodeCost(nodeId);
			cost = (cost == null || cost.equals(Double.NaN)) ? 0.0 : cost;
			if(cost != null )
			{
				costMap.put(nodeId, cost);
			}
		}
		return costMap;
	}
 	
	public NodeAverageCost getAverageCost()
	{
		return this.averageCost;
	}

	private static NodeEvaluator createNodeEvaluator()
	{
		NodeEvaluator evaluator = new NodeEvaluator();
		evaluator.setCPUWeight(CPU_WEIGHT);
		evaluator.setMemWeight(MEM_WEIGHT);
		evaluator.setNumberOfObjectsWeight(NUMBER_OBJECTS_WEIGHT);
		evaluator.setResponseTimeWeight(RESPONSE_TIME_WEIGHT);
		evaluator.setThroughputWeight(THROUGHPUT_WEIGHT);
		return evaluator;
	}
	
}
